package quarto.controller.client;

import java.util.ArrayList;

import quarto.Message;
import quarto.controller.Command;
import quarto.controller.Protocol;
import quarto.controller.client.strategy.Strategy;
import quarto.model.Board;
import quarto.model.InvalidPieceValueException;
import quarto.model.Piece;

/**
 * Representatie van een speler<br />
 * Handelt de binnenkomende commando's af
 * @knows
 * - Het communicatie protocol
 * @does
 * - Zetten vragen en doorsturen
 * @author Frank Borgonjen & Tim Smit
 */
public abstract class Player implements Protocol 
{
    /** de beschikbare commando's */
    private ArrayList<Command> commands;
    
    /** de client */
    protected Client           client;
    
    /** naam van de speler in het spel */
    private String             gameName;
    
    /** lock op het beginnen van makemove */
    private boolean            startMakeMoveLock;
    
    /** lock op het beginnen van makemove */
    private boolean            movedMakeMoveLock;
    
    /**
     * De strategie van de computer speler
     * @invariant strategy != null
     */
    protected Strategy strategy;
    
    /**
     * Legt de client vast in de speler
     * @param client
     */
    public void setClient(Client client)
    {
        this.client = client;
        
        // als er een strategy is, de client ook doorsturen
        if (this.strategy != null)
        {
            this.strategy.setClient(client);
        }
    }
    
    /**
     * Construeert een speler<br />
     * Maakt alles klaar om commando's te ontvangen
     */
    public Player()
    {
        // de locks klaarzetten
        this.startMakeMoveLock = true;
        this.movedMakeMoveLock = false;
        
        this.commands = new ArrayList<Command>();
        String regex;
        String d = "\\" + DELIM;
        String text = "([^" + DELIM + "]+)";
        String piece = 
            "(" + TRUE + "|" + FALSE + ")" + d + // kleur
            "(" + TRUE + "|" + FALSE + ")" + d + // grootte
            "(" + TRUE + "|" + FALSE + ")" + d + // vorm
            "(" + TRUE + "|" + FALSE + ")";      // dichtheid
        
        // Start commando
        regex = "^" + START + d +               // commando
                text + d +                      // eigen naam
                text + d +                      // naam tegenstander
                "(" + TRUE + "|" + FALSE + ")$"; // of chatten mogelijk is
        this.commands.add(new StartCommand(regex));
        
        // MakeMove commando
        regex = "^" + MAKEMOVE + d +    // commando
                piece + "$";            // stuk
        this.commands.add(new MakeMoveCommand(regex));

        // Moved commando
        regex = "^" + MOVED + d +       // commando
            "([0-9]{1,2})" + "(" + d +  // positie
            piece + ")?$";              // stuk
        this.commands.add(new MovedCommand(regex));
        
        // GameOver commando
        regex = "^" + GAMEOVER + "(" + d + // commando
                text + ")?$";              // winnaar
        this.commands.add(new GameOverCommand(regex));
        
        // Msg commando
        regex = "^" + MSG + d +         // commando
                text + "$";             // bericht
        this.commands.add(new MsgCommand(regex));
    }
    
    /**
     * Voert een commando uit zodra de string als uitvoerbaar gezien
     * wordt door een van de commands uit de lijst.
     * @param command String representatie van het commando dat 
     *        uitgevoerd moet worden
     */
    public synchronized void execute(String command)
    {
        boolean found = false;
        int i = 0;
        
        while (!found && i < this.commands.size())
        {
            if (this.commands.get(i).executable(command))
            {
                found = true;
                new Thread(this.commands.get(i)).start();
            }
            
            i++;
        }
        
        // er is geen geldig commando gevonden
        if (!found)
        {
            this.notifyClientUI(String.format(Message.ERROR_INVALID_COMMAND, command));
        }
    }
    
    /**
     * Verstuurt een bericht naar de ClientUI
     * @param msg het bericht dat verstuurd moet worden
     */
    public void notifyClientUI(String msg)
    {
        this.client.addMessage(msg);
    }
    
    
    // Interne klassen om commando's uit te voeren
    private class StartCommand extends Command
    {
        public StartCommand(String regex)
        {
            super(regex);
        }

        /**
         * Handelt het start commando af<br />
         * - laat de status zien van het op handen zijnde spelletje
         */
        public void run()
        {
            ArrayList<String> params = this.getParameters();
            // naam opslaan, want deze wordt ook gebruikt voor GameOver
            gameName = params.get(0);
            
            notifyClientUI(Message.PLAYER_STARTING);
            notifyClientUI(String.format(Message.PLAYER_OWN_NAME, params.get(0)));
            notifyClientUI(String.format(Message.PLAYER_OPPONENT_NAME, params.get(1)));
            
            if (params.get(2).equals(TRUE))
            {
                notifyClientUI(String.format(Message.PLAYER_CAN_CHAT, params.get(1)));
            }
            
            // Geeft aan de ClientGUI (indien dat het geval is) door wat de namen
            // van de spelers zijn
            client.setPlayerNames(params.get(0), params.get(1));
            
            // laat het bord zien.
            client.showBoard();
            
            // vergrendeling verwijderen
            try
            {
                startMakeMoveLock = false;
                this.notify();
            }
            catch (IllegalMonitorStateException e) {}
        }
    }
    
    /**
     * Commando dat gebruikt wordt om de speler op de hoogte te brengen dat
     * hij een zet mag doen met met meegegeven stuk.
     * De speler geeft een locatie terug, en zodra er nog stukken over zijn
     * ook een stuk voor de tegenstander
     */
    private class MakeMoveCommand extends Command
    {
        /**
         * Construeert een 'makemove' commando
         * @param patroon van het commando
         */
        public MakeMoveCommand(String regex)
        {
            super(regex);
        }

        /**
         * Handel het commando af<br />
         * - vraagt naar een locatie<br />
         * - vraagt naar een nieuw stuk voor de tegenstande
         */
        public void run()
        {
            // zolang er een slot op zit, wachten
            while (startMakeMoveLock || movedMakeMoveLock)
            {
                try
                {
                    this.wait();
                }
                catch (InterruptedException e) {}
                catch (IllegalMonitorStateException e) {}
            }
            
            // slot erop
            movedMakeMoveLock = true;
            
            // parameters ophalen
            ArrayList<String> params = this.getParameters();
            
            // stuk aanmaken aan de hand van de parameters
            Piece piece = client.getBoard().getPiece(params.get(0), params.get(1), params.get(2), params.get(3));
            
            // indien de clientUI een GUI is, zal dit huidige stuk zichtbaar worden
            client.showCurrentPiece(piece);
            
            // locatie bepalen voor het meegekregen stuk
            int location = -1;
            do
            {
                location = getLocationForPiece(piece);
                // kijken of het wel een geldige locatie is, en of de locatie
                // nog wel vrij is
                if (!client.getBoard().validAvailableLocation(location))
                {
                    notifyClientUI(String.format(Message.PLAYER_ERROR_INVALID_LOCATION, location));
                    location = -1;
                }
            }
            while (location < 0);
            
            // Als er nog stukken over zijn kunnen we een nieuw stuk bepalen
            // Aantal beschikbare stukken moet groter dan 1 zijn want het stuk 
            // dat deze speler moet zetten staat nog in de lijst
            if (client.getBoard().getAvailablePieces().size() > 1)
            {
                // kijken of de speler gewonnen heeft
                Board b = client.getBoard().deepCopy();
                b.setPiece(location, piece);
                Piece newPiece = null;
                
                // speler heeft gewonnen
                if (b.hasWinner())
                {
                    // het maakt niet uit welk stuk er gekozen wordt,
                    // kies een random stuk
                    int i = 0;
                    do
                    {
                        try
                        {
                            newPiece = new Piece(i++);
                        }
                        catch (InvalidPieceValueException ex) {}
                    }
                    while (!b.isAvailablePiece(newPiece) || newPiece.compare(piece));
                }
                // speler moet zelf een nieuw stuk uitkiezen
                else
                {
                    // nieuwe stuk bepalen voor de tegenstander
                    do
                    {
                        newPiece = getNewPiece();
                        if (!client.getBoard().isAvailablePiece(newPiece) || newPiece.compare(piece))
                        {
                            notifyClientUI(String.format(Message.PLAYER_ERROR_UNAVAILABLE_PIECE, piece.getDescription()));
                            newPiece = null;
                        }
                    }
                    while (newPiece == null);
                }
                
                // indien de clientUI een GUI is, zal het stuk voor de tegenstander zichtbaar worden
                client.showOpponentPiece(newPiece);
                
                client.sendMessage(MOVE + DELIM + location + DELIM + newPiece.getNetworkNotation());
            }
            else
            {
                client.sendMessage(MOVE + DELIM + location);
            }
        }
    }
    
    /**
     * Handelt het commando 'moved' af
     */
    private class MovedCommand extends Command
    {
        /**
         * construeert een moved commando
         * @param patroon van het commando
         */
        public MovedCommand(String regex)
        {
            super(regex);
        }
        
        /**
         * Handelt het commando af<br />
         * - verwerk de laatst gezette zet
         */
        public void run()
        {
            // beveiliging erop
            movedMakeMoveLock = true;
            
            // parameters ophalen
            ArrayList<String> params = this.getParameters();
            
            // locatie opslaan
            int location = Integer.parseInt(params.get(0));
            
            // het piece ophalen aan de hand van de parameters
            Piece piece = client.getBoard().getPiece(params.get(1), params.get(2), params.get(3), params.get(4));
            
            // bord updaten en weergeven
            client.getBoard().setPiece(location, piece);
            client.showBoard();
            
            // lock eraf
            try
            {
                movedMakeMoveLock = false;
                this.notify();
            }
            catch (IllegalMonitorStateException e) {}
        }
    }
    
    /**
     * Handelt het commando 'gameover' af
     */
    private class GameOverCommand extends Command
    {
        /**
         * construeerst een GameOverCommand
         * @param patroon van het commando
         */
    	public GameOverCommand(String regex)
    	{
    		super(regex);
    	}
        
        /**
         * Handelt het commando af
         * - kijk of de speler gewonnen heeft
         */
        public void run()
    	{
            // haal de parameters op
            ArrayList<String> params = this.getParameters();
            
            // als er geen stuk is meegegeven
    		if (params.size() == 0)
            {
                // Remise!
                notifyClientUI(Message.PLAYER_REMISE);
            }
            // kijken of de speler gewonnnen heeft
            else if (params.get(0).equals(gameName))
            {
                // GEWONNEN
                notifyClientUI(Message.PLAYER_WIN);
            }
            // de tegenstander heeft gewonnen
            else
            {
                // Verloren
                notifyClientUI(Message.PLAYER_LOSE);
            }
            
            // client afsluiten
            client.shutdown();
    	}
    }
    
    /**
     * Handelt het commando 'msg' af
     */
    private class MsgCommand extends Command
    {
        /**
         * construeert een MsgCommand
         * @param patroon van het commando
         */
    	public MsgCommand(String regex)
    	{
    		super(regex);
    	}
    	
        /**
         * Handelt het commando af
         * - geeft het bericht door naar de clientUI
         */
        public void run()
    	{
            // parameter ophalen
            ArrayList<String> params = this.getParameters();
            
            // bericht weergeven
    		notifyClientUI(params.get(0));
    	}
    }
    
    /**
     * Vraag om de locatie van het opgegeven stuk
     * @param het stuk
     * @return de locatie
     */
    public abstract int getLocationForPiece(Piece piece);
    
    /**
     * Vraag de gebruiker om een nieuw stuk
     * @return
     */
    public abstract Piece getNewPiece();
}
